﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using FM.Api.ViewModels;
using FM.Core.Storage;
using FM.Util;
using FM.Util.Helper;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;

namespace FileManage.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class FileController : ControllerBase
    {
        /*
        * 第一步，握手传递文件大小，多少片，md5值，文件类型
        * 第二步，分片上传文件片段，存储文件
        * 第三步，合并所有文件，做md5校验
        */


        /// <summary>
        /// 请求上传文件
        /// </summary>
        /// <param name="request">请求上传参数实体</param>
        [HttpPost("requestUpload")]
        public IActionResult RequestUpload(UploadFileRequest request)
        {
            LogUtil.Info("开始上传文件");

            if (request.Size <= 0 || request.Count <= 0 || string.IsNullOrEmpty(request.FileData))
            {
                return NotFound("参数有误");
            }
            else
            {
                //这里需要记录文件相关信息，并返回文件guid名，后续请求带上此参数
                string guidName = Guid.NewGuid().ToString("N");

                //前期单台服务器可以记录Cache，多台后需考虑redis或数据库
                CacheUtil.Set(guidName, request, new TimeSpan(0, 10, 0), true);
                return Ok(guidName);
            }
        }

        /// <summary>
        /// 文件上传
        /// </summary>
        [HttpPost, Route("upload")]
        public async Task<ActionResult<FileInfo>> UploadFileAsync()
        {
            var files = Request.Form.Files;
            long size = files.Sum(f => f.Length);
            string fileName = Request.Form["filename"];

            int.TryParse(Request.Form["fileindex"], out int fileIndex);
            LogUtil.Debug($"FileSave开始执行获取数据：{fileIndex}_{size}");

            if (size <= 0 || string.IsNullOrEmpty(fileName))
            {
                return BadRequest("文件上传失败");
            }

            if (!CacheUtil.Exists(fileName))
            {
                return BadRequest("请重新请求上传文件");
            }

            long fileSize = 0;
            string filePath = $".{ConfigHelper.GetSection("CommonSettings", "FilePath")}{DateTime.Now.ToString("yyyy-MM-dd")}/{fileName}";
            string saveFileName = $"{fileName}_{fileIndex}";
            string dirPath = Path.Combine(filePath, saveFileName);
            if (!Directory.Exists(filePath))
            {
                Directory.CreateDirectory(filePath);
            }

            foreach (var file in files)
            {
                //如果有文件
                if (file.Length > 0)
                {
                    fileSize = 0;
                    fileSize = file.Length;

                    using (var stream = new FileStream(dirPath, FileMode.OpenOrCreate))
                    {
                        await file.CopyToAsync(stream);
                    }
                }
            }

            return Ok(filePath);
        }


        /// <summary>
        /// 文件合并
        /// </summary>
        /// <param name="fileInfo">文件参数信息[name]</param>
        [HttpPost, Route("Merge")]
        public async Task<ActionResult> FileMerge([FromBody]Dictionary<string, object> fileInfo)
        {
            string fileName = string.Empty;
            if (fileInfo.ContainsKey("name"))
            {
                fileName = fileInfo["name"].ToString();
            }
            if (string.IsNullOrEmpty(fileName))
            {
                return BadRequest("文件名不能为空");
            }

            //最终上传完成后，请求合并返回合并消息
            try
            {
                RequestFileUploadRequest requestFile = CacheUtil.Get<RequestFileUploadRequest>(fileName);
                if (requestFile == null)
                {
                    return BadRequest("合并失败");
                }
                string filePath = $".{ConfigHelper.GetSection("CommonSettings", "FilePath")}{DateTime.Now.ToString("yyyy-MM-dd")}/{fileName}";

                LogUtil.Debug($"获取文件路径：{filePath}");
                LogUtil.Debug($"获取文件类型：{requestFile.FileExt}");

                string savePath = filePath.Replace(fileName, "");
                string saveFileName = $"{fileName}{requestFile.FileExt}";
                var files = Directory.GetFiles(filePath);
                string fileFinalName = Path.Combine(savePath, saveFileName);
                LogUtil.Debug($"获取文件最终路径：{fileFinalName}");
                FileStream fs = new FileStream(fileFinalName, FileMode.Create);
                LogUtil.Debug($"目录文件下文件总数：{files.Length}");

                LogUtil.Debug($"目录文件排序前：{string.Join(",", files.ToArray())}");
                LogUtil.Debug($"目录文件排序后：{string.Join(",", files.OrderBy(x => x.Length).ThenBy(x => x))}");
                byte[] finalBytes = new byte[requestFile.Size];
                foreach (var part in files.OrderBy(x => x.Length).ThenBy(x => x))
                {
                    var bytes = System.IO.File.ReadAllBytes(part);

                    await fs.WriteAsync(bytes, 0, bytes.Length);
                    bytes = null;
                    System.IO.File.Delete(part);//删除分块
                }
                fs.Close();
                //这个地方会引发文件被占用异常
                fs = new FileStream(fileFinalName, FileMode.Open);
                string strMd5 = GetCryptoString(fs);
                LogUtil.Debug($"文件数据MD5：{strMd5}");
                LogUtil.Debug($"文件上传数据：{JsonConvert.SerializeObject(requestFile)}");
                fs.Close();
                Directory.Delete(filePath);
                //如果MD5与原MD5不匹配，提示重新上传
                if (strMd5 != requestFile.FileData)
                {
                    LogUtil.Debug($"上传文件md5：{requestFile.FileData},服务器保存文件md5：{strMd5}");
                    return BadRequest("MD5值不匹配");
                }

                CacheUtil.Remove(fileInfo["name"].ToString());
                return Ok();
            }
            catch (Exception ex)
            {
                LogUtil.Error($"合并文件失败，文件名称：{fileName}，错误信息：{ex.Message}");
                return BadRequest("合并文件失败,请重新上传");
            }
        }


        /// <summary>
        /// 请求下载文件
        /// </summary>
        /// <param name="fileInfo">文件参数信息[name]</param>
        /// <returns></returns>
        [HttpPost, Route("RequestDownload")]
        public ActionResult RequestDownloadFile([FromBody]Dictionary<string, object> fileInfo)
        {

            string fileName = string.Empty;
            string fileExt = string.Empty;
            if (fileInfo.ContainsKey("name"))
            {
                fileName = fileInfo["name"].ToString();
            }
            if (fileInfo.ContainsKey("ext"))
            {
                fileExt = fileInfo["ext"].ToString();
            }
            if (string.IsNullOrEmpty(fileName))
            {
                return BadRequest("文件名不能为空");
            }
            //获取对应目录下文件，如果有，获取文件开始准备分段下载
            string filePath = $".{ConfigHelper.GetSection("CommonSettings", "FilePath")}{DateTime.Now.ToString("yyyy-MM-dd")}/{fileName}";
            filePath = $"{filePath}{fileExt}";
            FileStream fs = null;
            try
            {
                if (!System.IO.File.Exists(filePath))
                {
                    return BadRequest("文件尚未处理完");
                }
                fs = new FileStream(filePath, FileMode.Open);
                if (fs.Length <= 0)
                {
                    return BadRequest("文件尚未处理完");
                }
                int shardSize = 1 * 1024 * 1024;//一次1M
                RequestFileUploadRequest request = new RequestFileUploadRequest();

                request.Count = (int)(fs.Length / shardSize);
                if ((fs.Length % shardSize) > 0)
                {
                    request.Count += 1;
                }
                request.FileData = GetCryptoString(fs);

                return Ok(request);
            }
            catch (Exception ex)
            {
                LogUtil.Debug($"读取文件信息失败：{filePath}，错误信息：{ex.Message}");
                return BadRequest("文件上传失败,请重试");
            }
            finally
            {
                if (fs != null)
                {
                    fs.Close();
                }
            }
        }

        /// <summary>
        /// 分段下载文件
        /// </summary>
        /// <param name="fileInfo">请求参数信息[index,name]</param>
        /// <returns></returns>
        [HttpPost, Route("Download")]
        public async Task<IActionResult> FileDownload([FromBody]Dictionary<string, object> fileInfo)
        {
            //开始根据片段来下载
            int index = 0;
            if (fileInfo.ContainsKey("index"))
            {
                int.TryParse(fileInfo["index"].ToString(), out index);
            }
            else
            {
                return Ok(new { code = -1, msg = "缺少参数" });
            }
            string fileName = string.Empty;
            string fileExt = string.Empty;
            if (fileInfo.ContainsKey("name"))
            {
                fileName = fileInfo["name"].ToString();
            }
            if (fileInfo.ContainsKey("ext"))
            {
                fileExt = fileInfo["ext"].ToString();
            }
            if (string.IsNullOrEmpty(fileName))
            {
                return Ok(new { code = -1, msg = "文件名不能为空" });
            }
            //获取对应目录下文件，如果有，获取文件开始准备分段下载
            string filePath = $".{ConfigHelper.GetSection("CommonSettings", "FilePath")}{DateTime.Now.ToString("yyyy-MM-dd")}/{fileName}{fileExt}";

            if (!System.IO.File.Exists(filePath))
            {
                return Ok(new { code = -1, msg = "文件尚未处理" });
            }
            using (var fs = new FileStream(filePath, FileMode.Open))
            {
                if (fs.Length <= 0)
                {
                    return Ok(new { code = -1, msg = "文件尚未处理" });
                }
                int shardSize = 1 * 1024 * 1024;//一次1M
                int count = (int)(fs.Length / shardSize);
                if ((fs.Length % shardSize) > 0)
                {
                    count += 1;
                }
                if (index > count - 1)
                {
                    return Ok(new { code = -1, msg = "无效的下标" });
                }
                fs.Seek(index * shardSize, SeekOrigin.Begin);
                if (index == count - 1)
                {
                    //最后一片 = 总长 - (每次片段大小 * 已下载片段个数)
                    shardSize = (int)(fs.Length - (shardSize * index));
                }
                byte[] datas = new byte[shardSize];
                await fs.ReadAsync(datas, 0, datas.Length);
                return File(datas, "application/x-gzip");
            }
        }

        /// <summary>
        /// 文件流加密
        /// </summary>
        /// <param name="fileStream"></param>
        /// <returns></returns>
        private string GetCryptoString(Stream fileStream)
        {
            MD5 md5 = new MD5CryptoServiceProvider();
            byte[] cryptBytes = md5.ComputeHash(fileStream);
            return GetCryptoString(cryptBytes);
        }
        private string GetCryptoString(byte[] cryptBytes)
        {
            //加密的二进制转为string类型返回
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < cryptBytes.Length; i++)
            {
                sb.Append(cryptBytes[i].ToString("x2"));
            }
            return sb.ToString();
        }

    }
}
